#pragma once

#include "Core.h"

namespace Gut
{

    struct ShaderCode
    {
        unsigned int id = 0;
        bool CreateFrom(const char *code, int type);
        void Release();
        inline std::string GetShaderType(int type)
        {
            switch (type)
            {
            case GL_VERTEX_SHADER:
                return "Vertex";
            case GL_FRAGMENT_SHADER:
                return "Fragment";
            case GL_GEOMETRY_SHADER:
                return "Geometry";

            default:
                break;
            }
            return "Unknown";
        }



        inline bool checkCompileErrors(unsigned int shader, std::string type)
        {
            GLint success;
            GLchar infoLog[1024];
            if (type != "PROGRAM")
            {
                glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
                if (!success)
                {
                    glGetShaderInfoLog(shader, 1024, NULL, infoLog);
                    LOGE("ERROR::SHADER_COMPILATION_ERROR of type: %s \n %s", type.c_str(), infoLog);
                }
            }
            else
            {
                glGetProgramiv(shader, GL_LINK_STATUS, &success);
                if (!success)
                {
                    glGetProgramInfoLog(shader, 1024, NULL, infoLog);
                    LOGE("ERROR::PROGRAM_LINKING_ERROR of type: %s \n %s", type.c_str(), infoLog);
                }
            }
            return success;
        }
    };

    struct ShaderFile : public ShaderCode
    {
        bool CreateFromFile(std::string path, int type);
    };

    struct Shader
    {
    public:
        bool CreateFromCode(const char *vcode, const char *fCode);
        bool CreateFromFile(ShaderFile vFile, ShaderFile fFile);
        bool CreateFromFilePath(std::string vPath, std::string fPath);

#ifdef WIN32

        bool CreateFromFilePath(std::string vPath, std::string gPath, std::string fPath);
        bool CreateFormFile(ShaderFile vFile, ShaderFile gFile, ShaderFile fFile);

#endif // DEBUG

    public:
        unsigned int ID = 0;
        void Use() const;
        void Create();
        void Release();
        void Link();
        void UnUse() const;
        void Delete()
        {
            glDeleteShader(ID);
        }

        void Bind(const std::string &name, unsigned int binding)
        {
            glUniformBlockBinding(ID, GetUniformBlockIndex(name), binding);
        }

        unsigned int GetUniformBlockIndex(const std::string &name)
        {
            return glGetUniformBlockIndex(ID, name.c_str());
        }

        unsigned int GetLocation(const std::string &name) const
        {
            unsigned int res = glGetUniformLocation(ID, name.c_str());

#ifdef SHADER_DEBUG
            if (res == -1)
            {
                LOGE("index of location %s is -1", name.c_str());
            }
#endif // SHADER_DEBUG

            return res;
        }

        void setBool(const std::string &name, bool value) const
        {
            glUniform1i(GetLocation(name), (int)value);
        }
        // ------------------------------------------------------------------------
        void setInt(const std::string &name, int value) const
        {
            glUniform1i(GetLocation(name), value);
        }
        // ------------------------------------------------------------------------
        void setFloat(const std::string &name, float value) const
        {
            glUniform1f(GetLocation(name), value);
        }
        void SetFloatArray(const std::string &name, float *val, int num)
        {
            for (int i = 0; i < num; i++)
            {
                std::string label = name + std::string("[") + std::to_string(i) + "]";
                setFloat(label, val[i]);
            }
        }

        void setVec2(const std::string &name, const float *value) const
        {
            glUniform2fv(GetLocation(name), 1, &value[0]);
        }
        void setVec2(const std::string &name, float x, float y) const
        {
            glUniform2f(GetLocation(name), x, y);
        }
        // ------------------------------------------------------------------------
        void setVec3(const std::string &name, const float *value) const
        {
            glUniform3fv(GetLocation(name), 1, &value[0]);
        }
        void setVec3(const std::string &name, float x, float y, float z) const
        {
            glUniform3f(GetLocation(name), x, y, z);
        }
        // ------------------------------------------------------------------------
        void setVec4(const std::string &name, const float *value, const int num = 1) const
        {
            glUniform4fv(GetLocation(name), num, &value[0]);
        }
        void setVec4(const std::string &name, float x, float y, float z, float w)
        {
            glUniform4f(GetLocation(name), x, y, z, w);
        }
        // ------------------------------------------------------------------------
        void setMat2(const std::string &name, const float *mat) const
        {
            glUniformMatrix2fv(GetLocation(name), 1, GL_FALSE, &mat[0]);
        }
        // ------------------------------------------------------------------------
        void setMat3(const std::string &name, const float *mat) const
        {
            glUniformMatrix3fv(GetLocation(name), 1, GL_FALSE, &mat[0]);
        }
        // ------------------------------------------------------------------------
        void setMat4(const std::string &name, const float *mat) const
        {
            glUniformMatrix4fv(GetLocation(name), 1, GL_FALSE, &mat[0]);
        }

        void setVec2(const std::string& name, const glm::vec2& v) const
        {
            this->setVec2(name, &v.x);
        }

        void setVec3(const std::string& name, const glm::vec3& v) const
        {
            this->setVec3(name, &v.x);
        }

        void setVec4(const std::string& name, const glm::vec4& v) const
        {
            this->setVec4(name, &v.x);
        }

        void setMat2(const std::string &name, const glm::mat2 mat) const
        {
            this->setMat2(name, &mat[0][0]);
        }
        // ------------------------------------------------------------------------
        void setMat3(const std::string &name, const glm::mat3 mat) const
        {
            this->setMat3(name, &mat[0][0]);
        }
        void setMat4(const std::string& name, const glm::mat4& mat) const
        {
            this->setMat4(name, &mat[0][0]);
        }
    };

    struct ShaderVertexFramentDesc
    {
        const char *vcode;
        const char *fcode;
    };

    struct ShaderVertexFragment : public Shader
    {
    };

    struct ShaderVertexGeometryFragment : public Shader
    {
    };

}